JBoss Community Archive (Read Only)

WildFly 10

Remote EJB invocations via JNDI - EJB client API or remote-naming project

Purpose

WildFly provides EJB client API project as well as remote-naming project for invoking on remote objects exposed via JNDI. This article explains which approach to use when and what the differences and scope of each of these projects is.

History

Previous versions of JBoss AS (versions < WildFly 8) used JNP project (http://anonsvn.jboss.org/repos/jbossas/projects/naming/) as the JNDI naming implementation. Developers of client applications of previous versions of JBoss AS will be familiar with the jnp:// PROVIDER_URL URL they used to use in their applications for communicating with the JNDI server on the JBoss server.

Starting WildFly 8, the JNP project is not used. Neither on the server side nor on the client side. The client side of the JNP project has now been replaced by jboss-remote-naming project (https://github.com/jbossas/jboss-remote-naming). There were various reasons why the JNP client was replaced by jboss-remote-naming project. One of them was the JNP project did not allow fine grained security configurations while communicating with the JNDI server. The jboss-remote-naming project is backed by the jboss-remoting project (https://github.com/jboss-remoting/jboss-remoting) which allows much more and better control over security.

Overview

Now that we know that for remote client JNDI communication with WildFly 8 requires jboss-remote-naming project, let's quickly see what the code looks like.

Client code relying on jndi.properties in classpath

void doLookup() {
  // Create an InitialContext using the javax.naming.* API
  Context ctx = new InitialContext();
  ctx.lookup("foo/bar");
  ...
}

As you can see, there's not much here in terms of code. We first create a InitialContext (http://download.oracle.com/javase/6/docs/api/javax/naming/InitialContext.html) which as per the API will look for a jndi.properties in the classpath of the application. We'll see what our jndi.properties looks like, later. Once the InitialContext is created, we just use it to do a lookup on a JNDI name which we know is bound on the server side. We'll come back to the details of this lookup string in a while.

Let's now see what the jndi.properties in our client classpath looks like:

java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory
java.naming.provider.url=http-remoting://localhost:8080

Those 2 properties are important for jboss-remote-naming project to be used for communicating with the WildFly server. The first property tells the JNDI API which initial context factory to use. In this case we are pointing it to the InitailContextFactory class supplied by the jboss-remote-naming project. The other property is the PROVIDER_URL. Developers familiar with previous JBoss AS versions would remember that they used jnp://localhost:1099 (just an example). In WildFly, the URI protocol scheme for jboss-remote-naming project is remote://. The rest of the PROVIDER_URL part is the server hostname or IP and the port on which the remoting connector is exposed on the server side. By default the http-remoting connector port in WildFly 8 is 8080. That's what we have used in our example. The hostname we have used is localhost but that should point to the server IP or hostname where the server is running.

JNP client project in previous AS versions allowed a comma separated list for PROVIDER_URL value, so that if one of the server isn't accessible then the JNDI API would use the next available server. The jboss-remote-naming project has similar support starting 1.0.3.Final version of that project (which is available in a WildFly release after 7.1.1.Final).

WildFly 8 can use the PROVIDER_URL like:

java.naming.provider.url=http-remoting://server1:8080,http-remoting://server2:8080

So we saw how to setup the JNDI properties in the jndi.properties file. The JNDI API also allows you to pass these properties to the constructor of the InitialContext class (please check the javadoc of that class for more details). Let's quickly see what the code would look like:

void doLookup() {
  Properties jndiProps = new Properties();
  jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
  jndiProps.put(Context.PROVIDER_URL,"http-remoting://localhost:8080");
  // create a context passing these properties
  Context ctx = new InitialContext(jndiProps);
  // lookup
  ctx.lookup("foo/bar");
  ...
}

That's it! You can see that the values that we pass to those properties are the same as what we did via the jndi.properties. It's upto the client application to decide which approach they want to follow.

How does remoting naming work

We have so far had an overview of how the client code looks like when using the jboss-remote-naming (henceforth referred to as remote-naming - too tired of typing jboss-remote-naming everytime images/author/images/icons/emoticons/wink.gif ) project. Let's now have a brief look at how the remote-naming project internally establishes the communication with the server and allows JNDI operations from the client side.

Like previously mentioned, remote-naming internally uses jboss-remoting project. When the client code creates an InitialContext backed by the org.jboss.naming.remote.client.InitialContextFactory class, the org.jboss.naming.remote.client.InitialContextFactory internally looks for the PROVIDER_URL (and other) properties that are applicable for that context (doesn't matter whether it comes from the jndi.properties file or whether passed explicitly to the constructor of the InitialContext). Once it identifies the server and port to connect to, the remote-naming project internally sets up a connection using the jboss-remoting APIs with the remoting connector which is exposed on that port.

We previously mentioned that remote-naming, backed by jboss-remoting project, has increased support for security configurations. Starting WildFly 8, every service including the http remoting connector (which listens by default on port 8080), is secured (see https://community.jboss.org/wiki/AS710Beta1-SecurityEnabledByDefault for details). This means that when trying to do JNDI operations like a lookup, the client has to pass appropriate user credentials. In our examples so far we haven't passed any username/pass or any other credentials while creating the InitialContext. That was just to keep the examples simple. But let's now take the code a step further and see one of the ways how we pass the user credentials. Let's at the moment just assume that the remoting connector on port 8080 is accessible to a user named "peter" whose password is expected to be "lois".

Note: The server side configurations for the remoting connector to allow "peter" to access the connector, is out of the scope of this documentation. The WildFly 8 documentation already has chapters on how to set that up.

void doLookup() {
  Properties jndiProps = new Properties();
  jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
  jndiProps.put(Context.PROVIDER_URL,"http-remoting://localhost:8080");
  // username
  jndiProps.put(Context.SECURITY_PRINCIPAL, "peter");
  // password
  jndiProps.put(Context.SECURITY_CREDENTIALS, "lois");
  // create a context passing these properties
  Context ctx = new InitialContext(jndiProps);
  // lookup
  ctx.lookup("foo/bar");
  ...
}

The code is similar to our previous example, except that we now have added 2 additional properties that are passed to the InitialContext constructor. The first is http://docs.oracle.com/javase/6/docs/api/javax/naming/Context.html#SECURITY_PRINCIPAL which passes the username (peter in this case) and the second is http://docs.oracle.com/javase/6/docs/api/javax/naming/Context.html#SECURITY_CREDENTIALS which passes the password (lois in this case). Of course the same properties can be configured in the jndi.properties file (read the javadoc of the Context class for appropriate properties to be used in the jndi.properties). This is one way of passing the security credentials for JNDI communication with WildFly. There are some other ways to do this too. But we won't go into those details here for two reasons. One, it's outside the scope of this article and two (which is kind of the real reason) I haven't looked fully at the remote-naming implementation details to see what other ways are allowed.

JNDI operations allowed using remote-naming project

So far we have mainly concentrated on how the naming context is created and what it internally does when an instance is created. Let's now take this one step further and see what kind of operations are allowed for a JNDI context backed by the remote-naming project.

The JNDI Context has various methods http://docs.oracle.com/javase/6/docs/api/javax/naming/Context.html that are exposed for JNDI operations. One important thing to note in case of remote-naming project is that, the project's scope is to allow a client to communicate with the JNDI backend exposed by the server. As such, the remote-naming project does not support many of the methods that are exposed by the javax.naming.Context class. The remote-naming project only supports the read-only kind of methods (like the lookup() method) and does not support any write kind of methods (like the bind() method). The client applications are expected to use the remote-naming project mainly for lookups of JNDI objects. Neither WildFly 8 nor remote-naming project allows writing/binding to the JNDI server from a remote application.

requisites of remotely accessible JNDI objects

On the server side, the JNDI can contain numerous objects that are bound to it. However, not all of those are exposed remotely. The two conditions that are to be satisfied by the objects bound to JNDI, to be remotely accessible are:

1) Such objects should be bound under the java:jboss/exported/ namespace. For example, java:jboss/exported/foo/bar
2) Objects bound to the java:jboss/exported/ namespace are expected to be serializable. This allows the objects to be sent over the wire to the remote clients

Both these conditions are important and are required for the objects to be remotely accessible via JNDI.

JNDI lookup strings for remote clients backed by the remote-naming project

In our examples, so far, we have been consistently using "foo/bar" as the JNDI name to lookup from a remote client using the remote-naming project. There's a bit more to understand about the JNDI name and how it maps to the JNDI name that's bound on the server side.

First of all, the JNDI names used while using the remote-naming project are always relative to the java:jboss/exported/ namespace. So in our examples, we are using "foo/bar" JNDI name for the lookup, that actually is (internally) "java:jboss/exported/foo/bar". The remote-naming project expects it to always be relative to the "java:jboss/exported/" namespace. Once connected with the server side, the remote-naming project will lookup for "foo/bar" JNDI name under the "java:jboss/exported/" namespace of the server.

Note: Since the JNDI name that you use on the client side is always relative to java:jboss/exported namespace, you shouldn't be prefixing the java:jboss/exported/ string to the JNDI name. For example, if you use the following JNDI name:

ctx.lookup("java:jboss/exported/helloworld");

then remote-naming will translate it to

ctx.lookup("java:jboss/exported/java:jboss/exported/helloworld");

and as a result, will fail during lookup.

The remote-naming implementation perhaps should be smart enough to strip off the java:jboss/exported/ namespace prefix if supplied. But let's not go into that here.

How does remote-naming project implementation transfer the JNDI objects to the clients

When a lookup is done on a JNDI string, the remote-naming implementation internally uses the connection to the remoting connector (which it has established based on the properties that were passed to the InitialContext) to communicate with the server. On the server side, the implementation then looks for the JNDI name under the java:jboss/exported/ namespace. Assuming that the JNDI name is available, under that namespace, the remote-naming implementation then passes over the object bound at that address to the client. This is where the requirement about the JNDI object being serializable comes into picture. remote-naming project internally uses jboss-marshalling project to marshal the JNDI object over to the client. On the client side the remote-naming implementation then unmarshalles the object and returns it to the client application.

So literally, each lookup backed by the remote-naming project entails a server side communication/interaction and then marshalling/unmarshalling of the object graph. This is very important to remember. We'll come back to this later, to see why this is important when it comes to using EJB client API project for doing EJB lookups (EJB invocations from a remote client using JNDI) as against using remote-naming project for doing the same thing.

Summary

That pretty much covers whatever is important to know, in the remote-naming project, for a typical client application. Don't close the browser yet though, since we haven't yet come to the part of EJB invocations from a remote client using the remote-naming project. In fact, the motivation behind writing this article was to explain why not to use remote-naming project (in most cases) for doing EJB invocations against WildFly server.

Those of you who don't have client applications doing remote EJB invocations, can just skip the rest of this article if you aren't interested in those details.

Remote EJB invocations backed by the remote-naming project

In previous sections of this article we saw that whatever is exposed in the java:jboss/exported/ namespace is accessible remotely to the client applications under the relative JNDI name. Some of you might already have started thinking about exposing remote views of EJBs under that namespace.

It's important to note that WildFly server side already by default exposes the remote views of a EJB under the java:jboss/exported/ namespace (although it isn't logged in the server logs). So assuming your server side application has the following stateless bean:

package org.myapp.ejb;

@Stateless
@Remote(Foo.class)
public class FooBean implements Foo {
...
 public String sayBar() {
     return "Baaaaaaaar";
 }
}

Then the "Foo" remote view is exposed under the java:jboss/exported/ namespace under the following JNDI name scheme (which is similar to that mandated by EJB3.1 spec for java:global/ namespace):app-name

app-name/module-name/bean-name!bean-interface

where,

app-name = the name of the .ear (without the .ear suffix) or the application name configured via application.xml deployment descriptor. If the application isn't packaged in a .ear then there will be no app-name part to the JNDI string.
module-name = the name of the .jar or .war (without the .jar/.war suffix) in which the bean is deployed or the module-name configured in web.xml/ejb-jar.xml of the deployment. The module name is mandatory part in the JNDI string.
bean-name = the name of the bean which by default is the simple name of the bean implementation class. Of course it can be overridden either by using the "name" attribute of the bean definining annotation (@Stateless(name="blah") in this case) or even the ejb-jar.xml deployment descriptor.
bean-interface = the fully qualified class name of the interface being exposed by the bean.

So in our example above, let's assume the bean is packaged in a myejbmodule.jar which is within a myapp.ear. So the JNDI name for the Foo remote view under the java:jboss/exported/ namespace would be:

java:jboss/exported/myapp/myejbmodule/FooBean!org.myapp.ejb.Foo

That's where WildFly will automatically expose the remote views of the EJBs under the java:jboss/exported/ namespace, in addition to the java:global/ java:app/ java:module/ namespaces mandated by the EJB 3.1 spec.

Note that only the java:jboss/exported/ namespace is available to remote clients.

So the next logical question would be, are these remote views of EJBs accessible and invokable using the remote-naming project on the client application. The answer is yes! Let's quickly see the client code for invoking our FooBean. Again, let's just use "peter" and "lois" as username/pass for connecting to the remoting connector.

void doBeanLookup() {
  ...
  Properties jndiProps = new Properties();
  jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
  jndiProps.put(Context.PROVIDER_URL,"http-remoting://localhost:8080");
  // username
  jndiProps.put(Context.SECURITY_PRINCIPAL, "peter");
  // password
  jndiProps.put(Context.SECURITY_CREDENTIALS, "lois");
  // This is an important property to set if you want to do EJB invocations via the remote-naming project
  jndiProps.put("jboss.naming.client.ejb.context", true);
  // create a context passing these properties
  Context ctx = new InitialContext(jndiProps);
  // lookup the bean     Foo
  beanRemoteInterface = (Foo) ctx.lookup("myapp/myejbmodule/FooBean!org.myapp.ejb.Foo");
  String bar = beanRemoteInterface.sayBar();
  System.out.println("Remote Foo bean returned " + bar);
  ctx.close();
  // after this point the beanRemoteInterface is not longer valid!
}

As you can see, most of the code is similar to what we have been seeing so far for setting up a JNDI context backed by the remote-naming project. The only parts that change are:

1) An additional "jboss.naming.client.ejb.context" property that is added to the properties passed to the InitialContext constructor.
2) The JNDI name used for the lookup
3) And subsequently the invocation on the bean interface returned by the lookup.

Let's see what the "jboss.naming.client.ejb.context" does. In WildFly, remote access/invocations on EJBs is facilitated by the JBoss specific EJB client API, which is a project on its own https://github.com/jbossas/jboss-ejb-client. So no matter, what mechanism you use (remote-naming or core EJB client API), the invocations are ultimately routed through the EJB client API project. In this case too, the remote-naming internally uses EJB client API to handle EJB invocations. From a EJB client API project perspective, for successful communication with the server, the project expects a EJBClientContext backed by (atleast one) EJBReceiver(s). The EJBReceiver is responsible for handling the EJB invocations. One type of a EJBReceiver is a RemotingConnectionEJBReceiver which internally uses jboss-remoting project to communicate with the remote server to handle the EJB invocations. Such a EJBReceiver expects a connection backed by the jboss-remoting project. Of course to be able to connect to the server, such a EJBReceiver would have to know the server address, port, security credentials and other similar parameters. If you were using the core EJB client API, then you would have configured all these properties via the jboss-ejb-client.properties or via programatic API usage as explained here EJB invocations from a remote client using JNDI. But in the example above, we are using remote-naming project and are not directly interacting with the EJB client API project.

If you look closely at what's being passed, via the JNDI properties, to the remote-naming project and if you remember the details that we explained in a previous section about how the remote-naming project establishes a connection to the remote server, you'll realize that these properties are indeed the same as what the RemotingConnectionEJBReceiver would expect to be able to establish the connection to the server. Now this is where the "jboss.naming.client.ejb.context" property comes into picture. When this is set to true and passed to the InitialContext creation (either via jndi.properties or via the constructor of that class), the remote-naming project internally will do whatever is necessary to setup a EJBClientContext, containing a RemotingConnectionEJBReceiver which is created using the same remoting connection that is created by and being used by remote-naming project for its own JNDI communication usage. So effectively, the InitialContext creation via the remote-naming project has now internally triggered the creation of a EJBClientContext containing a EJBReceiver capable of handling the EJB invocations (remember, no remote EJB invocations are possible without the presence of a EJBClientContext containing a EJBReceiver which can handle the EJB).

So we now know the importance of the "jboss.naming.client.ejb.context" property and its usage. Let's move on the next part in that code, the JNDI name. Notice that we have used the JNDI name relative to the java:jboss/exported/ namespace while doing the lookup. And since we know that the Foo view is exposed on that JNDI name, we cast the returned object back to the Foo interface. Remember that we earlier explained how each lookup via remote-naming triggers a server side communication and a marshalling/unmarshalling process. This applies for EJB views too. In fact, the remote-naming project has no clue (since that's not in the scope of that project to know) whether it's an EJB or some random object.

Once the unmarshalled object is returned (which actually is a proxy to the bean), the rest is straightforward, we just invoke on that returned object. Now since the remote-naming implementation has done the necessary setup for the EJBClientContext (due to the presence of "jboss.naming.client.ejb.context" property), the invocation on that proxy will internally use the EJBClientContext (the proxy is smart enough to do that) to interact with the server and return back the result. We won't go into the details of how the EJB client API handles the communication/invocation.

Long story short, using the remote-naming project for doing remote EJB invocations against WildFly is possible!

Why use the EJB client API approach then?

I can guess that some of you might already question why/when would one use the EJB client API style lookups as explained in the EJB invocations from a remote client using JNDI article instead of just using (what appears to be a simpler) remote-naming style lookups.

Before we answer that, let's understand a bit about the EJB client project. The EJB client project was implemented keeping in mind various optimizations and features that would be possible for handling remote invocations. One such optimization was to avoid doing unnecessary server side communication(s) which would typically involve network calls, marshalling/unmarshalling etc... The easiest place where this optimization can be applied, is to the EJB lookup. Consider the following code (let's ignore how the context is created):

ctx.lookup("foo/bar");

Now foo/bar JNDI name could potentially point to any type of object on the server side. The jndi name itself won't have the type/semantic information of the object bound to that name on the server side. If the context was setup using the remote-naming project (like we have seen earlier in our examples), then the only way for remote-naming to return an object for that lookup operation is to communicate with the server and marshal/unmarshal the object bound on the server side. And that's exactly what it does (remember, we explained this earlier).

The EJB client API project on the other hand optimizes this lookup. In order to do so, it expects the client application to let it know that a EJB is being looked up. It does this, by expecting the client application to use the JNDI name of the format "ejb:" namespace and also expecting the client application to setup the JNDI context by passing the "org.jboss.ejb.client.naming" value for the Context.URL_PKG_PREFIXES property.

Example:

final Properties jndiProperties = new Properties();
jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
// create the context
final Context context = new InitialContext(jndiProperties);

// lookup
Foo beanProxy = context.lookup("ejb:myapp/myejbmodule//FooBean!org.myapp.ejb.Foo");
String bar = beanProxy.sayBar();

More details about such code can be found here EJB invocations from a remote client using JNDI

When a client application looks up anything under the "ejb:" namespace, it is a clear indication (for the EJB client API project) to know that the client is looking up an EJB. That's where it steps in to do the necessary optimizations that might be applicable. So unlike, in the case of remote-naming project (which has no clue about the semantics of the object being looked up), the EJB client API project does not trigger a server side communication or a marshal/unmarshal process when you do lookup for a remote view of a stateless bean (it's important to note that we have specifically mentioned stateless bean here, we'll come to that later). Instead, the EJB client API just returns a java.lang.reflect.Proxy instance of the remote view type that's being looked up. This not just saves a network call, marshalling/unmarshalling step but it also means that you can create an EJB proxy even when the server isn't up yet. Later on, when the invocation on the proxy happens, the EJB client API does communicate with the server to carry out the invocation.

Is the lookup optimization applicable for all bean types?

In the previous section we (intentionally) mentioned that the lookup optimization by the EJB client API project happens for stateless beans. This kind of optimization is not possible for stateful beans because in case of stateful beans, a lookup is expected to create a session for that stateful bean and for session creation we do have to communicate with the server since the server is responsible for creating that session.

That's exactly why the EJB client API project expects the JNDI name lookup string for stateful beans to include the "?stateful" string at the end of the JNDI name:

context.lookup("ejb:myapp/myejbmodule//StatefulBean!org.myapp.ejb.Counter?stateful");

Notice the use of "?stateful" in that JNDI name. See EJB invocations from a remote client using JNDI for more details about such lookup.

The presence of "?stateful" in the JNDI name lookup string is a directive to the EJB client API to let it know that a stateful bean is being looked up and it's necessary to communicate with the server and create a session during that lookup.

So as you can see, we have managed to optimize certain operations by using the EJB client API for EJB lookup/invocation as against using the remote-naming project. There are other EJB client API implementation details (and probably more might be added) which are superior when it is used for remote EJB invocations in client applications as against remote-naming project which doesn't have the intelligence to carry out such optimizations for EJB invocations. That's why the remote-naming project for remote EJB invocations is considered "deprecated". Note that if you want to use remote-naming for looking up and invoking on non-EJB remote objects then you are free to do so. In fact, that's why that project has been provided. You can even use the remote-naming project for EJB invocations (like we just saw), if you are fine with not wanting the optimizations that the EJB client API can do for you or if you have other restrictions that force you to use that project.

Restrictions for EJB's

If the remote-naming is used there are some restrictions as there is no full support of the ejb-client features.

  • No loadbalancing, if the URL conatains multiple "remote://" servers there is no loadbalancing, the first available server will be used and only in case it is not longer available there will be a failover to the next available one.

  • No cluster support. As a cluster needs to be defined in the jboss-ejb-client.properties this feature can not be used and there is no cluster node added

  • No client side interceptor. The EJBContext.getCurrent() can not be used and it is not possible to add a client interceptor

  • No UserTransaction support

  • All proxies become invalid if .close() for the related Initalcontext is invoked, or the InitialContext is not longer referenced and gets garbage-collected. In this case the underlying EJBContext is destroyed and the conections are closed.

  • It is not possible to use remote-naming if the client is an application deployed on another JBoss instance

JBoss.org Content Archive (Read Only), exported from JBoss Community Documentation Editor at 2020-03-13 13:40:59 UTC, last content change 2014-05-11 12:06:21 UTC.